/*
** Copyright (C) 2010 Calin Radoni
**
** This file is part of CHScanner.
**
** CHScanner is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** CHScanner is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with CHScanner. If not, see <http://www.gnu.org/licenses/>.
*/

#include "StdAfx.h"
#include ".\scanmethoddns.h"

ScanMethodDns::ScanMethodDns(void) : ScanMethod()
{
	myType = __wmtDns;

	rBuffer1[0]  = 0x07;	// len = 7
	rBuffer1[1]  = 0x69;	// 'i'
	rBuffer1[2]  = 0x6E;	// 'n'
	rBuffer1[3]  = 0x2D;	// '-'
	rBuffer1[4]  = 0x61;	// 'a'
	rBuffer1[5]  = 0x64;	// 'd'
	rBuffer1[6]  = 0x64;	// 'd'
	rBuffer1[7]  = 0x72;	// 'r'
	rBuffer1[8]  = 0x04;	// len = 4
	rBuffer1[9]  = 0x61;	// 'a'
	rBuffer1[10] = 0x72;	// 'r'
	rBuffer1[11] = 0x70;	// 'p'
	rBuffer1[12] = 0x61;	// 'a'
	rBuffer1[13] = 0x00;	// len = 0 (address done)
	rBuffer1[14] = 0x00;	//
	rBuffer1[15] = 0x0C;	// TYPE: PTR
	rBuffer1[16] = 0x00;	//
	rBuffer1[17] = 0x01;	// CLASS: IN

	rBuffer2[0] = 0xC0;	//
	rBuffer2[1] = 0x0C;	// Name
	rBuffer2[2] = 0x00;	//
	rBuffer2[3] = 0x0C;	// TYPE: PTR
	rBuffer2[4] = 0x00;	//
	rBuffer2[5] = 0x01;	// CLASS: IN

	rBuffer4[0]  = 0x03;	// len = 3
	rBuffer4[1]  = 0x69;	// 'i'
	rBuffer4[2]  = 0x70;	// 'p'
	rBuffer4[3]  = 0x36;	// '6'
	rBuffer4[4]  = 0x04;	// len = 4
	rBuffer4[5]  = 0x61;	// 'a'
	rBuffer4[6]  = 0x72;	// 'r'
	rBuffer4[7]  = 0x70;	// 'p'
	rBuffer4[8]  = 0x61;	// 'a'
	rBuffer4[9]  = 0x00;	// len = 0 (address done)
	rBuffer4[10] = 0x00;	//
	rBuffer4[11] = 0x0C;	// TYPE: PTR
	rBuffer4[12] = 0x00;	//
	rBuffer4[13] = 0x01;	// CLASS: IN
}

ScanMethodDns::~ScanMethodDns(void)
{
}

//--------------------------------------------------------------------

int ScanMethodDns::InternalInit(void)
{
	bool err;

	FillRandomBuffer();

	winPcap->FillDnsMAC(dnsMAC);

	err = true;
	if(dnsMAC[0]!=0x00) err = false;
	if(dnsMAC[1]!=0x00) err = false;
	if(dnsMAC[2]!=0x00) err = false;
	if(dnsMAC[3]!=0x00) err = false;
	if(dnsMAC[4]!=0x00) err = false;
	if(dnsMAC[5]!=0x00) err = false;
	if(err) return MSR_ERR_DNS_MAC_NOT_FOUND;
	
	if(workPhase==__wptDiscovery) return MSR_ERR_OK;
	if(workPhase==__wptScan) return MSR_ERR_OK;

	return MSR_ERR_PHASE;
}

//--------------------------------------------------------------------

int ScanMethodDns::InternalExecute(void)
{
	ScanPacket* pack;
	in_addr6 ia6;
	bool hunted;

	if(addrList==NULL) return MSR_ERR_PARAMETER;

	ScanPair sp;
	while(GetNextPair(&sp, __sptIpOnly) && !stopStopStop){
		if(PacketsUsed()>0){
			CheckResponses();
			CheckDeaths();
		}

		if(sp.addr!=NULL){
			if(!sp.addr->addrIP.IsEmpty()){
				if(config->xMinDelay>0){
					if(lastScanTime!=0){
						waitDone = false;
						while(!waitDone){
							CheckResponses();
							SleepEx(1, TRUE);
							scanTime = GetTickCount();
							scanTime = ComputeDeltaTime(lastScanTime, scanTime);
							if(scanTime>=config->xMinDelay)
								waitDone = true;
						}
					}
					lastScanTime = GetTickCount();
				}

				pack = FindFirstUnusedPacket();
				while(pack==NULL){
					SleepEx(waitTime, TRUE);
					CheckResponses();
					CheckDeaths();
					pack = FindFirstUnusedPacket();
				}

				if(pack!=NULL){
					pack->Initialize(true);
					pack->sTime       = GetTickCount();
					pack->addr        = sp.addr->addrIP.GetD();
					pack->port        = 0;
					pack->methodType  = myType;
					pack->fullAddress = sp.addr;
					pack->machineInfo = FindMachineByIp(pack->addr);

					if(pack->machineInfo==NULL) hunted = false;
					else                        hunted = pack->machineInfo->hunted;
					if(!hunted){
						if(!SendDnsQueryRRPTR(sp.addr))
							pack->Initialize(false);

						if((packSent % notifUdp) == 0 && packSent>0 && !embeddedMode)
							if(hWnd!=NULL)
								PostMessage(hWnd, CHS_MSG_PACKETS_SENT, 0, (LPARAM)packSent);
					}
				}
			}
			if(!sp.addr->addrIPv6.IsEmpty()){
				sp.addr->addrIPv6.Get(&ia6);
				pack = FindFirstUnusedPacket();
				while(pack==NULL){
					SleepEx(waitTime, TRUE);
					CheckResponses();
					CheckDeaths();
					pack = FindFirstUnusedPacket();
				}

				if(pack!=NULL){
					pack->Initialize(true);
					pack->sTime       = GetTickCount();
					sp.addr->addrIPv6.Get(&pack->addr6);
					pack->port        = 0;
					pack->methodType  = myType;
					pack->fullAddress = sp.addr;
					pack->machineInfo = FindMachineByIp6(&ia6);

					if(pack->machineInfo==NULL) hunted = false;
					else                        hunted = pack->machineInfo->hunted;
					if(!hunted){
						if(!SendDnsQueryRRPTR6(sp.addr))
							pack->Initialize(false);

						if((packSent % notifUdp) == 0 && packSent>0 && !embeddedMode)
							if(hWnd!=NULL)
								PostMessage(hWnd, CHS_MSG_PACKETS_SENT, 0, (LPARAM)packSent);
					}
				}
			}
		}
	}

	// wait until the end
	while(PacketsUsed()>0){
		CheckResponses();
		CheckDeaths();
		SleepEx(1, TRUE);
	}

	return MSR_ERR_OK;
}

//--------------------------------------------------------------------
void ScanMethodDns::ComputeRandomValues(void)
{
}

bool ScanMethodDns::SendDnsQueryRRPTR(MFullAddress* addrIn)
{
	BYTE srcMac[6];
	BYTE dstMac[6];
	bool res;

	if(addrIn==NULL){
		// nothing to send
		return false;
	}

	ComputeRandomValues();

	res = packetBuilderEx->BuildPackDNSQueryRRPTR(winPcap->GetLocalIP(), winPcap->GetDnsServerIP(), addrIn->addrIP.GetD());
	if(!res) return false;

	winPcap->FillSrcMAC(&srcMac[0]);
	winPcap->FillDnsMAC(&dstMac[0]);
	res = packetBuilderEx->AddEthernetHeader(srcMac, dstMac, 0x0800);
	if(!res) return false;

	return SendPacket(packetBuilderEx->GetPacket(), (int)packetBuilderEx->GetPacketSize());
}

bool ScanMethodDns::SendDnsQueryRRPTR6(MFullAddress* addrIn)
{
	BYTE srcMac[6];
	BYTE dstMac[6];
	in_addr6 ia6;
	bool res;

	if(addrIn==NULL){
		// nothing to send
		return false;
	}

	ComputeRandomValues();

	addrIn->addrIPv6.Get(&ia6);

	res = packetBuilderEx->BuildPackDNSQueryRRPTR6(winPcap->GetLocalIP(), winPcap->GetDnsServerIP(), &ia6);
	if(!res) return false;

	winPcap->FillSrcMAC(&srcMac[0]);
	winPcap->FillDnsMAC(&dstMac[0]);
	res = packetBuilderEx->AddEthernetHeader(srcMac, dstMac, 0x0800);
	if(!res) return false;

	return SendPacket(packetBuilderEx->GetPacket(), (int)packetBuilderEx->GetPacketSize());
}

//---------------------------------------------------------------------------

void ScanMethodDns::FormatName(BYTE* dataIn, int sizeIn)
{
	int i;
	bool done;

	done = false;
	for(i=0; i<sizeIn && !done; i++){
		if(dataIn[i]==0) done = true;
		else{
			if(dataIn[i]<32)
				dataIn[i] = '.';
		}
	}
}

void ScanMethodDns::ProcessUdp(BYTE* dataIn, int sizeIn)
{
	IPHeaderPG       *iph;
	UDPHeaderPG      *udph;
	DNSRequestHeader *dnrh;
	int              dataOffset;
	ScanPacket       *pack;
	MMachineInfo     *machineInfo;
	WORD             flags;
	int              len;
	int              idx;
	DWORD            queryAddress;
	in_addr6		 queryAddress6;
	int              querySize;
	int              queryAddressType;
	WORD             *wLen;
	MMachineInfo     diMach;
	in_addr          dnsTTL;
	MDnsName         dnsName;

	iph = (IPHeaderPG*)dataIn;
	dataOffset  = (int)(iph->HLV & 0x0F);
	dataOffset *= 4;
	if(sizeIn<(dataOffset + 8)){
		// incomplete packet
		return;
	}

	udph = (UDPHeaderPG*)&dataIn[dataOffset];
	if(ntohs(udph->SourcePort)!=53){
		// not a DNS packet, skipping
		return;
	}

	len = ntohs(udph->Length);
	if(sizeIn<(len+dataOffset)){
		// incomplete packet
		return;
	}

	dnrh = (DNSRequestHeader*)&dataIn[dataOffset+8];
	flags = ntohs(dnrh->flags);
	if(!(flags & 0x8000)){
		// not a DNS response packet, skipping
		return;
	}
	flags &= 0x000F;
	if(flags!=0){
		// the DNS response indicates an error, skipping
		return;
	}

	if(dnrh->questions!=256){ // (ntohs(256)==1 in case I am wondering later...)
		// I only ask a question at a time  !!! 
		return;
	}

	if(dnrh->answerRR==0){
		// no response ?
		return;
	}

	// next, at dataIn[dataOffset + 8 + 12]
	//   - the query
	//   - the answer
	idx = dataOffset + 8 + 12;
	if(idx>sizeIn){
		// incomplete, skip
		return;
	}

	queryAddressType = ExtractAddressFromResponse(&dataIn[idx], sizeIn-idx, &queryAddress, &queryAddress6, &querySize);
	if(queryAddressType<0){
		// not what I am expecting, skip
		return;
	}

	idx += querySize;
	if(idx>sizeIn){
		// incomplete, skip
		return;
	}

	if(memcmp(&dataIn[idx], rBuffer2, 6)!=0)
		return;

	dnsTTL.S_un.S_un_b.s_b1 = dataIn[idx+9];
	dnsTTL.S_un.S_un_b.s_b2 = dataIn[idx+8];
	dnsTTL.S_un.S_un_b.s_b3 = dataIn[idx+7];
	dnsTTL.S_un.S_un_b.s_b4 = dataIn[idx+6];

	idx += 10;					// skip 4 bytes for DNS record's TTL
	if(idx>sizeIn){
		// incomplete, skip
		return;
	}

	wLen = (WORD*)&dataIn[idx];
	len = ntohs(*wLen);

	memset(rBuffer3, 0, 256);
	if(len>255){
		// nasol, nume mai mare decat am prevazut
		len = 255;
	}
	memcpy(rBuffer3, &dataIn[idx+2], len);
	idx += len + 2;
	if(idx>sizeIn){
		// incomplete, skip
		return;
	}

	FormatName(&rBuffer3[1], len-1);

	if(queryAddressType==0){
		// was an IPv4 query

		pack = FindPacket(queryAddress, 0, myType);
		if(pack!=NULL){
			switch(workPhase){
				case __wptDiscovery :
					diMach.Initialize();
					diMach.address.LoadFromPointer(pack->fullAddress);
					diMach.address.alive = true;
					dnsName.nameDns    = (char*)&rBuffer3[1];;
					dnsName.nameDnsTTL = dnsTTL.S_un.S_addr;
					dnsName.nameType   = 1;
					diMach.AddDnsNameNoDuplicates(&dnsName);
					machInfoList->Add(&diMach);
					break;

				case __wptScan :
					machineInfo = pack->machineInfo;
					if(machineInfo!=NULL){
						dnsName.nameDns    = (char*)&rBuffer3[1];;
						dnsName.nameDnsTTL = dnsTTL.S_un.S_addr;
						dnsName.nameType   = 1;
						machineInfo->AddDnsNameNoDuplicates(&dnsName);
					}
					break;

				default:
					break;
			}
			pack->Initialize(false);
		}

		return;
	}

	if(queryAddressType==1){
		// was an IPv6 query

		pack = FindPacket6(&queryAddress6, 0, myType);
		if(pack!=NULL){
			switch(workPhase){
				case __wptDiscovery :
					diMach.Initialize();
					diMach.address.LoadFromPointer(pack->fullAddress);
					diMach.address.alive = true;
					dnsName.nameDns    = (char*)&rBuffer3[1];;
					dnsName.nameDnsTTL = dnsTTL.S_un.S_addr;
					dnsName.nameType   = 28;
					diMach.AddDnsNameNoDuplicates(&dnsName);
					machInfoList->AddIPv6(&diMach);
					break;

				case __wptScan :
					machineInfo = pack->machineInfo;
					if(machineInfo!=NULL){
						dnsName.nameDns    = (char*)&rBuffer3[1];;
						dnsName.nameDnsTTL = dnsTTL.S_un.S_addr;
						dnsName.nameType   = 28;
						machineInfo->AddDnsNameNoDuplicates(&dnsName);
					}
					break;

				default:
					break;
			}
			pack->Initialize(false);
		}

		return;
	}
}

BYTE ScanMethodDns::HexaCharToByte(char chIn)
{
	if(chIn>='0' && chIn<='9')
		return (BYTE)(chIn - '0');
	if(chIn>='A' && chIn<='Z')
		return (BYTE)(chIn - 'A' + 10);
	if(chIn>='a' && chIn<='z')
		return (BYTE)(chIn - 'a' + 10);
	return 0;
}

int ScanMethodDns::ExtractAddressFromResponse(BYTE* dataIn, int sizeIn, DWORD* addrOut, in_addr6* addrOut6, int* lenOut)
{
	if(dataIn==NULL)   return -1;
	if(sizeIn<1)       return -1;
	if(addrOut==NULL)  return -1;
	if(addrOut6==NULL) return -1;
	if(lenOut==NULL)   return -1;

	BYTE tokenLen;
	BYTE chBuff[4];
	int idx, addrSize, i;
	in_addr ia;
	BYTE bbh, bbl;
	bool done, err, isIPv4;

	/* 
		- first compute de size of the address from query
			- if the size is 74 (64 + 10(=='03'ip6'04'arpa'00')) then it was a query for an IPv6 address
			- if the size is between 8-16 + 14 <--> 22 - 30 it is an IPv4 address
				(8 is for something like x.x.x.x)
				(16 is for something like xxx.xxx.xxx.xxx)
				(14 is for '07'in-addr'04'arpa'00')
		- then decode as appropiate
	*/

	idx  = 0;
	done = false;
	err  = false;
	while(!done){
		tokenLen = (BYTE)dataIn[idx];
		if(tokenLen==0){
			idx++;	// skip the length of the length
			done = true;
		}
		else{
			idx++;	// skip the length of the length
			idx += (unsigned int)tokenLen;	// skip this token
			if(idx>=sizeIn || idx>=128){
				// incomplete packet or malformed packet
				done = true;
				err = true;
			}
		}
	}
	if(err==true) return -1;

	addrSize = idx;

	if(addrSize<22) return -1;
	if(addrSize<=30) isIPv4 = true;
	else{
		if(addrSize!=74) return -1;
		isIPv4 = false;
	}

	if(isIPv4){
		*addrOut = 0;
		*lenOut  = 0;

		idx = 0;

		memset(chBuff, 0, 4);
		tokenLen = (unsigned int)dataIn[idx];
		if(tokenLen>3) return -1;
		memcpy(chBuff, &dataIn[idx+1], tokenLen);
		ia.S_un.S_un_b.s_b4 = atoi((char*)chBuff);
		idx += tokenLen + 1;

		memset(chBuff, 0, 4);
		tokenLen = (unsigned int)dataIn[idx];
		if(tokenLen>3) return -1;
		memcpy(chBuff, &dataIn[idx+1], tokenLen);
		ia.S_un.S_un_b.s_b3 = atoi((char*)chBuff);
		idx += tokenLen + 1;

		memset(chBuff, 0, 4);
		tokenLen = (unsigned int)dataIn[idx];
		if(tokenLen>3) return -1;
		memcpy(chBuff, &dataIn[idx+1], tokenLen);
		ia.S_un.S_un_b.s_b2 = atoi((char*)chBuff);
		idx += tokenLen + 1;

		memset(chBuff, 0, 4);
		tokenLen = (unsigned int)dataIn[idx];
		if(tokenLen>3) return -1;
		memcpy(chBuff, &dataIn[idx+1], tokenLen);
		ia.S_un.S_un_b.s_b1 = atoi((char*)chBuff);
		idx += tokenLen + 1;

		// dataIn[idx...] should be '07'in-addr'04'arpa'00000C0001
		// 07 69 6e 2d 61 64 64 72 04 61 72 70 61 00, 00 0C, 00 01

		if(memcmp(rBuffer1, &dataIn[idx], 18)!=0)
			return -1;

		*addrOut = ia.S_un.S_addr;
		*lenOut  = idx + 18;

		return 0;
	}

	// now processing the IPv6 request
	for(i=0; i<16; i++){
		bbh = HexaCharToByte(dataIn[4*i + 3]);
		bbl = HexaCharToByte(dataIn[4*i + 1]);
		addrOut6->u.Byte[15 - i] = ((bbh << 4) & 0xF0) | (bbl & 0x0F);
	}

	// dataIn[64...] should be '03'ip6'04'arpa'00000C0001
	// 03 69 70 36 04 61 72 70 61 00, 00 0C, 00 01
	if(memcmp(rBuffer4, &dataIn[64], 14)!=0)
		return -1;

	*lenOut = 78;

	return 1;
}

//---------------------------------------------------------------------------
